 
/* 
 * Copyright (C) Ignacio Lucas
 */
 
/*
 *  server_worker, hecho por Ignacio Lucas
 *
 *  Libreria que encpasula un worker del servidor, capaz de cambiar contextos mediante manejo de corrutinas.
 *
 *  Ignacio Lucas <ignacio.lucas@gmail.com>
 *
 */

#include "common.h"
#include "worker.h"
#include "server_worker.h"
#include "coroutine.h"
#include "queue.h"
#include "jobs_controller.h"
#include "job_event.h"
#include "socket.h"
#include "utils/object_pool.h"
#include "user_util.h"
#include "priority_queue.h"

// Este status de tarea solo puede asignarse dentro de este modulo.
#define SERVER_WORKER_JOB_INIT			0

// estructura que representa a un server_worker.
typedef struct s_server_worker
{
		// worker asociado con esta instancia de server_worker.
		worker_t worker;
		
		// Corrutina principal de server worker, a la que se vuelve cuando un job bloquearia o finaliza.
		ucontext_t main_coroutine;
		
		// jobs_controller de la aplicacion.
		jobs_controller_t jobs_controller;
		
		// utilidad para loguear.
		// log_handler_t log_handler;
		
		// job que actualmente esta ejecutando este server_worker
		server_worker_job_t current_job;
		
		/* 
		Ultima tarea ejecutada hasta el final. Esto se necesita ya que la destruccion de una tarea debe ser hecha fuera de la 
		corrutina en la que se ejecuto. Esta variable se ejecuta luego de la funcion server_worker_wrapper_function and debe 
		ser puesta a NULL en la corrutina principal.
		*/
		server_worker_job_t last_completed_job;
		
		/* 
			marca que indica si hay un evento pendiente de ser configurado en el modulo de manejo de eventos. La configuracion
			de eventos se hace dentro de la corrutina principal de server_worker.
			Cuando hay un evento pendiente de registrarse, pending_event_assign = 1.
			Una vez informado el evento pendiente de configuracion, el valor de esta marca
			debe volverse a 0 en la corrutina principal de este server_worker.
		*/
		int pending_event_assign;
		
		// buffer para log.
		// char log_buffer[2048];
		
} server_worker;

// estructura que representa una tarea que ejecuta server_worker.
typedef struct s_server_worker_job
{
	// Funcion que procesara esta tarea.
	int (*proc)(server_worker_job_t ,void *);
	
	// argumentos pasados a proc al invocar dicha funcion.
	void *args;
	
	// Puntero donde cada server_worker puede guardar datos temporarios de esta tarea. 
	// Es obligacion del worker alocar y liberar memoria de este puntero.
	void *user_data;
	/*
	 Si status es SERVER_WORKER_JOB_INIT o SERVER_WORKER_JOB_DONE entonces worker es NULL
	 Si status es SERVER_WORKER_JOB_RUNNING entonces worker se refiere al server_worker que esta ejecutando la tarea.
	 Si status es SERVER_WORKER_JOB_WAITING or SERVER_WORKER_JOB_RUNNABLE entonces worker refiere al ultimo worker que ejecuto esta tarea.
  */
	server_worker_t worker;
	
	// corrutina donde se ejecuta esta tarea.
	ucontext_t job_coroutine;
	
	// Registra el evento pendiente de ser enviado para su configuracion.
	job_event outstanding_event;
	
	// Ver constantes SERVER_WORKER_JOB_ (INIT | RUNNING | WAITING | RUNNABLE | DONE).
	int status;
	
	// posible job padre que lanzo a este job.
	server_worker_job_t parent_job;
	
	// posibles hijos que este job lanza.
	server_worker_job_children children_jobs;
	
	// prioridad con la que el job es inicialmente disparado. Ver priority_queue.h
	int launch_priority;

} server_worker_job;

// metodos de configuracion.
static size_t server_worker_stack_size = 256 * K;

size_t server_worker_get_stack_size()
{
	return server_worker_stack_size;
}

void server_worker_set_stack_size(size_t ss)
{
	server_worker_stack_size = ss;
}

// devuelve el tamanio de una tarea.
size_t server_worker_get_server_worker_job_struct_size()
{
	// la tarea contendra en si misma el stack de ejecucion, para poder alocar memoria
	// una sola vez y tener el job y el stack juntos.
	size_t tamanio_job = sizeof(server_worker_job);

	// sumamos el tamanio del stack
	tamanio_job += server_worker_get_stack_size();

	return tamanio_job;	
}


// utilidades para loguear.
//static int server_worker_log(server_worker_t sw_t, enum LOG_LEVEL log_level, char *msg);
//static int server_worker_log_fmt(server_worker_t sw_t, enum LOG_LEVEL log_level, char *fmt, ...);
static void server_worker_run_job(server_worker_job *job, server_worker *wk);

// nuevas utilidades de log.
//int log_info(char *msg);


// se envia a jobs_controller un job_event.
static inline int server_worker_register_event(job_event_t event);



// registra en el modulo de eventos el interes en ser notificado para un evento particular.
static void server_worker_assign_pending_event_on_current_job(server_worker *wk)
{
	int ret=0, socket_get_previous_events = 0;
	server_worker_job * job = NULL;
	job_event_t event = NULL;
	socket_t st = NULL;
	
	// Recupero la tarea que esta ejecutando el worker.
	job=(server_worker_job *)wk->current_job;
	
	if (job == NULL)
	{
		// Nada que hacer, significa que el worker no esta ejecutando ninguna tarea.
		return;
	}
	
	if (wk->pending_event_assign == 0)
	{
		// Nada que hacer, significa que la tarea llego a su fin.
		return;
	}
	
	// Importante: Reseteo la marca pending_event_assign!
	wk->pending_event_assign = 0;
	
	// Verificamos que tipo de evento se quiere configurar.
	event=&(job->outstanding_event);
	
	if (event->source == JOB_SOURCE_SOCKET)
	{
		st = event->data.sock_conf.st;
		socket_get_previous_events = socket_get_registered_events(st);
		
		// Antes de registrar el evento, registramos que el socket ya tiene eventos registrados. Si la registracion falla, volvemos a su valor anterior.
		socket_set_registered_events(st, event->asked_events);
	}
	
	// Informar a jobs_controller sobre la configuracion de interes.
	ret=server_worker_register_event((job_event_t)event);
	
	//logger_workers_debug("success agregado de evento %p para eventos=%d", event, event->asked_events);
	if (ret != 0)
	{
		/* 
			No se pudo registrar la configuracion del evento de interes.
			Volvemos entonces a la corrutina de la tarea informando del error!
		*/
		logger_workers_error("error configurando evento. Ptr: %p. Eventos de interes: %d !", event, event->asked_events);
		
		// Forzamos el error.
		event->fired_events = JOB_EVENT_ERR;
		
		if (event->source == JOB_SOURCE_SOCKET)
		{
			// Recuperamos el valor anterior de eventos previos configurados en el socket.
			socket_set_registered_events(st,socket_get_previous_events);
		}
		logger_workers_error("relanzando tarea %p para notificar error al configurar evento. Ptr: %p. Eventos de interes: %d !", job, event, event->asked_events);
		server_worker_run_job(job, wk);
	}
}

// se envia a jobs_controller un event
static inline int server_worker_register_event(job_event_t event)
{
	return jobs_controller_register_event_interest((job_event_t)event);
}

static inline int server_worker_notify_child_finished(server_worker_job_t childjob)
{
	job_event_t event = &(childjob->outstanding_event);
	// el job interesado en el evento es el parent.
	event->job = childjob->parent_job;
	
	event->source = JOB_SOURCE_JOB_COMM;
	event->data.server_worker_job_comm_conf.operation = SERVER_WORKER_JOB_OP_CHILD_FINISHED;
	// el job que configura el evento es el childjob.
	event->data.server_worker_job_comm_conf.job = childjob;
	event->operation = JOB_EVENT_OP_ADD;
	// aqui no hay asked_events, en realidad estoy disparando un evento.
	event->fired_events = JOB_EVENT_CHILD_FINISHED;

	return server_worker_register_event(event);	
}

// si la ultima tarea en ejecucion finalizo, liberamos los recursos tomados por esta.
static void server_worker_cleanup_last_completed_job(server_worker *wk)
{
	server_worker_job *last_completed_job = (server_worker_job *)wk->last_completed_job;
	int ret=0;
	
	if (last_completed_job != NULL)
	{
		// Liberacion de memoria de la corrutina.
		// worker_common_objects_pool_put(STACK_POOL, last_completed_job->job_coroutine.uc_stack.ss_sp);
		
		last_completed_job->job_coroutine.uc_stack.ss_sp=NULL;

		/* 
		analizamos la situacion de este job finalizado. Puede tratarse de:
		1) un job que corre de forma exclusiva (sin padre que lo haya lanzado) y que tampoco haya lanzado jobs hijos.
		2) un job que finalizo y es hijo de otro job.

		En la situacion 1 podemos liberar memoria del job, pero en el segundo debemos esperar a que el padre nos libere por lo que notificamos
		la finalizacion de la tarea, aguardando luego la liberacion.
		*/
		if (last_completed_job->parent_job == NULL)
		{
			server_worker_job_destroy((server_worker_job_t)last_completed_job);
		}
		else
		{
			// informamos al jobs_controller que, como tarea hija, terminamos.
			//logger_workers_info("notificando child finished!");
			ret=server_worker_notify_child_finished((server_worker_job_t)last_completed_job);
			if (ret != 0)
			{
				logger_workers_error("error notificando child finished: %d!", ret);
			}			
		}
		
		// borramos la referencia de la ultima tarea ejecutada y completada, preparando para una nueva tarea.
		wk->last_completed_job = NULL;
	}
}

// funcion que invoca internamente a la tarea que representa la aplicacion del usuario.
static void server_worker_wrapper_function(server_worker_job *job)
{
	int ret=0;
	// No puedo registrar quien es el worker del job en este punto ya que al finalizar el job el worker puede ser otro.
	// Entonces registro su valor posterior a la ejecucion del job.
	server_worker *wk = NULL;
	
	// invoca a la aplicacion del usuario.
	ret=(int)job->proc(job, job->args);
	logger_workers_debug("finalizacion de corrutina con ret=%d!", ret);
	
	// Registro la ultima tarea completada en worker.
	wk=(server_worker *)job->worker;
	wk->last_completed_job = job;
	
	// Registramos que no estamos ejecutando ninguna tarea y que no hay eventos pendientes de configuracion.
	wk->current_job=NULL;
	wk->pending_event_assign=0;

	// volvemos a la corrutina principal.	
	setcontext(&(job->worker->main_coroutine));
}

// cambiamos de ejecucion a la tarea.
static void server_worker_run_job(server_worker_job *job, server_worker *wk)
{
	swapcontext(&(wk->main_coroutine), &(job->job_coroutine));
}

// reserva de memoria para el stack de una corrutina.
static int server_worker_alloc_context(ucontext_t *newcontext, ucontext_t *back_context, server_worker_job *job, server_worker *sw)
{
	//int size=0;
	//void *stack = NULL;
	
	//size = server_worker_get_stack_size();

	// tomo memoria del common_objects_pool para STACKS.
	// stack = (char *)worker_common_objects_pool_get(STACK_POOL);

	/*
	if (stack == NULL)
	{
		return 1;
	}
	*/

	// tomo el estado actual de ejecucion.	
	if (getcontext(newcontext))
	{
		// error al tomar el estado actual de ejecucion.
		// worker_common_objects_pool_put(STACK_POOL, stack);
		// stack = NULL;
		newcontext->uc_stack.ss_sp = NULL;
		return 2;
	}

	// preparo la corrutina. 
	// newcontext->uc_link = NULL;
	// newcontext->uc_stack.ss_sp = stack;
	// newcontext->uc_stack.ss_size = (size);
	// newcontext->uc_stack.ss_flags = 0;
 
	// creamos el contexto de la corrutina, indicandole el procedimiento que debe ejecutar.
	makecontext(newcontext,(void *)server_worker_wrapper_function,1,job);
	return 0;
}

// inicializa la corrutina principal del server_worker.
static int server_worker_init_main_context(ucontext_t *main_context)
{
	main_context->uc_link = NULL;
	main_context->uc_stack.ss_sp = NULL;
	main_context->uc_stack.ss_size = 0;
	main_context->uc_stack.ss_flags = 0;
	return 0;
}

// utilidades de log para el server_worker.
/*static int server_worker_log(server_worker_t sw_t, enum LOG_LEVEL log_level, char *msg)
{
	server_worker *sw = (server_worker *)sw_t;
	sprintf(sw->log_buffer, "[worker=%d]", worker_get_id(sw->worker));
	return logger_log_fmt(logger_manager_get_worker_logger(), log_level, "%s %s", sw->log_buffer, msg);
}

// utilidades de log para el server_worker_job.
int server_worker_job_log(server_worker_job_t job_t, enum LOG_LEVEL log_level, char *msg)
{
	server_worker_job *job = (server_worker_job *)job_t;
	return server_worker_log(job->worker, log_level, msg);
}*/


/*int log_info(char *msg)
{
	return server_worker_log(user_util_get_current_job()->worker, LOG_LEVEL_INFO, msg);
}*/

// utilidades de log para el server_worker.
/*static int server_worker_log_fmt(server_worker_t sw_t, enum LOG_LEVEL log_level, char *fmt, ...)
{
  va_list args;
  int buf_size=0;
  server_worker *sw = (server_worker *)sw_t;

	sprintf(sw->log_buffer, "[worker=%d] ", worker_get_id(sw->worker));

	buf_size=sizeof(sw->log_buffer) - strlen(sw->log_buffer);
  va_start(args,fmt);
  vsnprintf(sw->log_buffer + strlen(sw->log_buffer), buf_size, fmt, args);
  sw->log_buffer[sizeof(sw->log_buffer)-1] = '\0';
  va_end(args);
  return logger_log(logger_manager_get_worker_logger(), log_level, sw->log_buffer);
}*/

// utilidades de log para el server_worker_job.
/*int server_worker_job_log_fmt(server_worker_job_t job_t, enum LOG_LEVEL log_level, char *fmt, ...)
{
  va_list args;
  int buf_size=0;
  server_worker *sw = (server_worker *)(((server_worker_job *)job_t)->worker);

	sprintf(sw->log_buffer, "[worker=%d] ", worker_get_id(sw->worker));

	buf_size=sizeof(sw->log_buffer) - strlen(sw->log_buffer);
  va_start(args,fmt);
  vsnprintf(sw->log_buffer + strlen(sw->log_buffer), buf_size, fmt, args);
  sw->log_buffer[sizeof(sw->log_buffer)-1] = '\0';
  va_end(args);
  return logger_log(logger_manager_get_worker_logger(), log_level, sw->log_buffer);
}*/


// preparacion de la tarea a ejecutar.
static int server_worker_prepare_job(server_worker_job *job, server_worker *sw)
{
	int ret = 0;
	
	job->worker = (server_worker_t)sw;
	
	// chequeo de posibles errores en la invocacion. No se puede:
	// - preparar la ejecucion de una tarea ya finalizada.
	// - intentar ejecutar una tarea que este esperando una condicion para ejecutar.
	// - intentar ejecutar una tarea que ya se esta ejecutando.
	
	//if ((job->status == SERVER_WORKER_JOB_DONE) || (job->status == SERVER_WORKER_JOB_WAITING) || (job->status == SERVER_WORKER_JOB_RUNNING))
	if ((job->status != SERVER_WORKER_JOB_RUNNABLE) && (job->status != SERVER_WORKER_JOB_INIT))
	{
		logger_workers_error("%s '%d'","no es posible correr un job con status", job->status);
		return -1;
	}
	

	//if (job->status == SERVER_WORKER_JOB_RUNNABLE)
	//{
		//server_worker_log(sw,(enum LOG_LEVEL)LOG_LEVEL_INFO,"retomando un job existente!");
	//}
	
	// alocamos contexto de ejecucion si se trata de un job nuevo.
	if (job->status == SERVER_WORKER_JOB_INIT)
	{
		// Si es una nueva tarea, preparo el contexto para su ejecucion.
		if ((ret=server_worker_alloc_context(&(job->job_coroutine),&(sw->main_coroutine), job, sw)) != 0)
		{
			logger_workers_error("%s","alloc_context falla!");
			return ret;
		}
	}
	// Cambio el status de la tarea a RUNNING.
	job->status = SERVER_WORKER_JOB_RUNNING;
	sw->current_job = job;

	// reflejamos el current job en nuestro worker.
	worker_set_user_data((void *)job);

	//logger_workers_error("poniendo job en user_data: %p",job);

	return 0;
}

/**
	Implementacion de la funcion principal de la corrutina de server_worker.
**/
static void* server_worker_main(void *server_wk)
{
	server_worker *sw=(server_worker *)server_wk;
	int ret=0;
	
	while (1)
	{
		// Chequeo si hay eventos pendientes de configuracion.
		server_worker_assign_pending_event_on_current_job(sw);
		
		// Borramos la ultima tarea completada (si existe).
		server_worker_cleanup_last_completed_job(sw);
		
		// pedimos una nueva tarea.
		server_worker_job *job = (server_worker_job *)worker_pull_job(sw->worker);

		// preparamos la ejecucion de la tarea.
		if ((ret=server_worker_prepare_job(job, sw)) == 0)
		{
			// cambiamos la ejecucion de la corrutina principal a la corrutina de la tarea.
			server_worker_run_job(job, sw);
		}
		else
		{
			logger_workers_error("%s%d", "no es posible ejecutar job.ret=", ret);
		}
	}
	return NULL;
}


server_worker_t server_worker_init(queue_t queue, jobs_controller_t jc, int id)
{
		int worker_ret=0, main_context_ret=0;
		server_worker *sw = NULL;

		//log_handler_t logger = logger_manager_get_worker_logger();
		
		sw = (server_worker*)malloc(sizeof(server_worker));
		if (sw == NULL)
		{
				logger_workers_error("%s","error al alocar memoria para server_worker");
				return NULL;
		}
		
		sw->jobs_controller = jc;
		sw->current_job = NULL;
		//sw->log_handler=logger;
		sw->last_completed_job = NULL;

		// Preparo la corrutina principal de server_worker
		
		main_context_ret=server_worker_init_main_context(&(sw->main_coroutine));
		if (main_context_ret != 0)
		{
			logger_workers_error( "%s %d. ret=%d", "error al alocar memoria para main_context, worker", id, main_context_ret);
			free(sw);
			sw=NULL;
			return NULL;
		}
				
		// Preparo al worker asociado a este server_worker.
		/*
			Aca separe el init de worker del start (el que inicia la thread) ya que sw->worker 
			en algunos casos no se llegaba a cargar que ya hab�a empezado la thread, 
			lo quer�a referenciar y estaba en null --> segmentation fault.
		*/
		sw->worker = worker_init((void *)server_worker_main, sw, queue, 32*K, id);
		if (sw->worker == NULL)
		{
			logger_workers_error( "%s %d", "error al alocar memoria para worker", id);
			free(sw);
			sw=NULL;
			return NULL;
		}
			
		// arranca la ejecucion de este worker.
		worker_ret=worker_start(sw->worker);
		if (worker_ret!=0)
		{
			logger_workers_error( "%s %d", "error al iniciar worker", id);
			server_worker_destroy(sw);
			return NULL;
		}
	  return (server_worker_t)sw;
}

server_worker_job_t server_worker_get_current_job(server_worker_t swt)
{
	return ((server_worker *)swt)->current_job;
}

int server_worker_destroy(server_worker_t swt)
{
	int ret=0;
	
	server_worker *sw = (server_worker *)swt;
	if (sw != NULL)
	{
		if (sw->worker != NULL)
		{
			//id = worker_get_id(sw->worker);
			ret=worker_destroy(sw->worker);
			sw->worker=NULL;
		}
		else
		{
			ret=1;				
		}
		free(sw);sw=NULL;
	}
	else
	{
		ret=2;
	}
	return ret;
}


static inline void server_worker_job_clear_children(server_worker_job *job)
{
	job->children_jobs.count=0;
	memset(job->children_jobs.child,0,SERVER_WORKER_JOB_MAX_CHILDREN*sizeof(server_worker_job_t));
}

// funciones de las tareas.
server_worker_job_t server_worker_job_init()
{
	server_worker_job * job = NULL;
	
	job = (server_worker_job *) common_objects_pool_get(JOB_POOL);
	if (job == NULL)
	{
		return NULL;
	}
	job->status = SERVER_WORKER_JOB_INIT;
	job->proc = NULL;
	job->args = NULL;
	job->user_data = NULL;
	job->worker=NULL;
	job->parent_job=NULL;
	
	server_worker_job_clear_children(job);

	// apuntamos el stack y su tamanio entendiendo que el stack comienza al finalizar el job.
	// logger_workers_error( "puntero de JOB: %p, puntero de stack: %p", job, ((void *)job) + sizeof(server_worker_job));



	job->job_coroutine.uc_stack.ss_sp = ((void *)job) + sizeof(server_worker_job);
	job->job_coroutine.uc_stack.ss_size = server_worker_get_stack_size();
	job->job_coroutine.uc_stack.ss_flags = 0;
	job->launch_priority = MASHUP_SERVER_PRIORITY_QUEUE_NORMAL;
	return job;
}

int server_worker_job_set_launch_priority(server_worker_job_t job_t, int launch_priority)
{
	if (priority_queue_is_valid(launch_priority))
	{
		((server_worker_job *)job_t)->launch_priority = launch_priority;
		return 0;
	}
	return 1;
}

int server_worker_job_get_launch_priority(server_worker_job_t job_t)
{
	return ((server_worker_job *)job_t)->launch_priority;
}

int server_worker_job_reset(server_worker_job_t job_t)
{
	server_worker_job * job = (server_worker_job *)job_t;
	
	job->status = SERVER_WORKER_JOB_INIT;
	job->proc = NULL;
	job->args = NULL;
	job->user_data = NULL;
	job->worker=NULL;
	job->launch_priority = MASHUP_SERVER_PRIORITY_QUEUE_NORMAL;
	
	return 0;
}

void server_worker_job_set_proc(server_worker_job_t job_t, 	int (*proc)(server_worker_job_t,void *))
{
	server_worker_job *job=(server_worker_job *)job_t;
	job->proc=proc;
}

void* server_worker_job_get_proc(server_worker_job_t job_t)
{
	server_worker_job *job=(server_worker_job *)job_t;
	return job->proc;
}

void server_worker_job_set_args(server_worker_job_t job_t, 	void *args)
{
	server_worker_job *job=(server_worker_job *)job_t;
	job->args=args;
}

void* server_worker_job_get_args(server_worker_job_t job_t)
{
	server_worker_job *job=(server_worker_job *)job_t;
	return job->args;
}

void server_worker_job_set_user_data(server_worker_job_t job_t, void *user_data)
{
	server_worker_job *job=(server_worker_job *)job_t;
	job->user_data=user_data;
}

void* server_worker_job_get_user_data(server_worker_job_t job_t)
{
	server_worker_job *job=(server_worker_job *)job_t;
	return job->user_data;
}

server_worker_t server_worker_job_get_server_worker(server_worker_job_t job_t)
{
	server_worker_job *job=(server_worker_job *)job_t;
	return job->worker;
}

void server_worker_job_set_status(server_worker_job_t job_t, int status)
{
	server_worker_job *job=(server_worker_job *)job_t;
	job->status=status;
}

int server_worker_job_get_status(server_worker_job_t job_t)
{
	server_worker_job *job=(server_worker_job *)job_t;
	return job->status;
}

int server_worker_job_destroy(server_worker_job_t job_t)
{
	server_worker_job *job=(server_worker_job *)job_t;
	if (job != NULL)
	{
		job->parent_job=NULL;
		job->job_coroutine.uc_link = NULL;
		job->job_coroutine.uc_stack.ss_sp=NULL;
		job->job_coroutine.uc_stack.ss_size = 0;
		job->job_coroutine.uc_stack.ss_flags = 0;
		common_objects_pool_put(JOB_POOL, job);
	}
	job=NULL;
	return 0;
}


job_event_t server_worker_job_get_outstanding_event(server_worker_job_t job_t)
{
	return &(((server_worker_job *)job_t)->outstanding_event);
}

int server_worker_job_is_outstanding_event(server_worker_job_t job_t)
{
	return (server_worker_job_get_status(job_t) == SERVER_WORKER_JOB_WAITING)?1:0;
}

int server_worker_job_unregister_events(server_worker_job_t job_t, socket_t st)
{
	server_worker_job *job=(server_worker_job *)job_t;
	job_event_t event = &(job->outstanding_event);
	int ret=0;
	
	event->source=JOB_SOURCE_SOCKET;
	event->data.sock_conf.st = st;
	event->asked_events=JOB_EVENT_ERR | JOB_EVENT_HUP;
	if (socket_get_registered_events(st) == 0)
	{
		// significa que al socket no tenia eventos registrados.
		return 1;
	}
	event->operation = JOB_EVENT_OP_DEL;
	event->job = job_t;
	
	ret=jobs_controller_register_event_interest((job_event_t)event);
	return ret;
}


/*
funcion invocada desde la libreria de sockets al momento de detectar que una operacion
de Entrada/Salida bloquearia.
En este caso, se detiene la corrutina de ejecucion y se vuelve a la corrutina principal.
*/
int server_worker_job_register_event_and_stop(server_worker_job_t job_t, int source, void *source_config, int events)
{
	server_worker_job *job=NULL;
	job_event_t event = NULL;
	server_worker *wk = NULL;
	int ret=0, saved_context = 0;
	int current_events=0;

	job=(server_worker_job *)job_t;
	event=&(job->outstanding_event);
	wk = (server_worker *)job->worker;
	
	// Asigno el tipo de objeto asociado al evento (para esta implementacion, solo manejamos socket).
	if (source == JOB_SOURCE_SOCKET)
	{
		// no todos los source requieren configurar asked_events y job (algunos ya lo traen configurado en outstanding_event).
		// para el caso del socket SI tengo que hacerlo.
		event->asked_events=events;
		event->job = job_t;

		// todo el manejo de sockets es no persistente y edge triggered.
		event->persistent=0;
		event->edge_or_level=0;
		
		current_events=socket_get_registered_events(((socket_config *)source_config)->st);
		event->source=JOB_SOURCE_SOCKET;
		event->data.sock_conf.st = ((socket_config *)source_config)->st; 	// Registro el socket.

		// Registramos posible configuracion de timeout.
		if (events & JOB_EVENT_TIMEOUT)
		{
			// tomamos el valor que configura el usuario es decir, el delta_timeout.
			event->timeout_config.delta_timeout = ((socket_config *)source_config)->timeout_config.delta_timeout;
		}

		// si ya tenia eventos registrados en el socket es una modificacion (MOD), sino es agregar eventos por primera vez (ADD).
		event->operation = (current_events == 0)?JOB_EVENT_OP_ADD:JOB_EVENT_OP_MOD;
	}

	//logger_workers_info("registrar contexto de job!");
	
	/**
		http://publib.boulder.ibm.com/infocenter/zos/v1r10/index.jsp?topic=/com.ibm.zos.r10.bpxbd00/rswctx.htm
	
		int swapcontext(ucontext_t *__restrict__ oucp, const ucontext_t *__restrict__ ucp);
		
		The swapcontext() function saves the current user context in the context structure pointed 
		to by oucp and restores the user context structure pointed to by ucp. 
		swapcontext() is equivalent to getcontext() with the oucp argument followed by setcontext() with the ucp argument.
	**/
	//}
	
	// Guardo el estado actual de ejecucion de la corrutina.
	if (getcontext(&(job->job_coroutine)) < 0)
	{
		logger_workers_error("error al registrar contexto de job: %s", strerror(errno));
		return 1;
	}
	
	/*
		Esto es necesario para no pasar 2 veces por esta parte de codigo, en el momento que se retome la corrutina.
		saved_context se le da valor 1. Luego, cuando se retome esta corrutina, se retomara en la linea posterior a
		getcontext. El primer retorno de getconext tendra saved_context=0 y se configurara el evento pendiente.
		Cuando se retome la corrutina, el valor saved_context sera 1 por lo que no se hara mas que volver
		a la funcion que invoco a "server_worker_job_register_event_and_stop".
	*/
	if (saved_context == 0)
	{
		// Registro que ya resguarde el contexto de ejecucion.
		saved_context = 1;
		
		// Importante: informo a server_worker que tiene eventos pendientes de asignacion.
		wk->pending_event_assign=1;
		
		// Volvemos a la corrutina principal para que alli se registre el evento de interes.
		setcontext(&(wk->main_coroutine));
	}
	return ret;
}

server_worker_job_t server_worker_job_get_parent(server_worker_job_t job_t)
{
	return ((server_worker_job *)job_t)->parent_job;
}

server_worker_job_comm_config_t  server_worker_job_get_job_comm_config(server_worker_job_t job_t)
{
	return &(((server_worker_job*)job_t)->outstanding_event.data.server_worker_job_comm_conf);
}

server_worker_job_t server_worker_job_prepare_priority(server_worker_job_t parent_job, int (*server_implementation)(server_worker_job_t,void *), void *args,int priority)
{
	server_worker_job_t child = server_worker_job_init();
	
	if (child == NULL)
	{
		// sin recursos?
		logger_workers_error("%s","sin recursos para lanzar nuevo job hijo");
		return NULL;
	}

	// le indicamos a este nuevo job quien es su padre.
	child->parent_job = parent_job;
	
	// inicializacion de la tarea.
	server_worker_job_set_proc(child,server_implementation);
	server_worker_job_set_args(child,args);
	server_worker_job_set_launch_priority(child, priority);
				
	// agregamos el job a la lista de tareas a enviar a la cola.
	jobs_controller_launch_child(child);
	return child;
}

server_worker_job_t server_worker_job_prepare(server_worker_job_t parent_job, int (*server_implementation)(server_worker_job_t,void *), void *args)
{
	return server_worker_job_prepare_priority(parent_job, server_implementation,args, MASHUP_SERVER_PRIORITY_QUEUE_NORMAL);
}



int server_worker_job_wait_children(server_worker_job_t parent_job, server_worker_job_children *children)
{
	server_worker_job_comm_config_t comm_config = server_worker_job_get_job_comm_config(parent_job);
	job_event_t event = &(parent_job->outstanding_event);
	
	comm_config->operation = SERVER_WORKER_JOB_OP_PARENT_WAITING;
	comm_config->pending_children = children->count;
	// el job que configura el evento es el parent_job.
	comm_config->job = parent_job;
	
	// registramos en parent_job todos los children que necesita esperar.
	memcpy((void *)(comm_config->children_jobs.child), (const void *)(children->child), children->count * sizeof(server_worker_job_t));
	
	// el job interesado en el evento es el parent.
	event->job = parent_job;
	event->source = JOB_SOURCE_JOB_COMM;
	event->operation = JOB_EVENT_OP_ADD;
	// aqui no hay asked_events, en realidad estoy disparando un evento.
	event->fired_events = JOB_EVENT_PARENT_WAITING;

	// aqui si o si tenemos que volver a la corrutina principal para registar el evento de parent_waiting fuera de la propia corrutina del padre.
	// para evitar problemas de concurrencia. 
	// el source_config es ignorado cuando el source no es JOB_SOURCE_SOCKET
	return server_worker_job_register_event_and_stop(parent_job, JOB_SOURCE_JOB_COMM, NULL, JOB_EVENT_PARENT_WAITING);
	
}

